// Copyright 2022 Huawei Cloud Computing Technology Co., Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "CaeSocketAgent.h"

#include "cJSON.h"

#include "CaeAgent.h"
#include "../cae_common/CaeCommon.h"
#include "../cae_service/CaeHttpsJob.h"
#include "../cae_service/CaeParamStorage.h"
#include "../cae_common/CaeMsgCode.h"

static const uint32_t MAX_INTERVAL = 1000;

CaeSocketAgent &CaeSocketAgent::GetInstance()
{
    static CaeSocketAgent instance;
    return instance;
}

void CaeSocketAgent::LockClientSocket()
{
    pthread_mutex_lock(&m_clientSocketLock);
}

void CaeSocketAgent::UnlockClientSocket()
{
    pthread_mutex_unlock(&m_clientSocketLock);
}

bool CaeSocketAgent::IsSocketValid()
{
    return m_sendFlag && m_clientSocket != nullptr;
}

CaeSocketAgent::CaeSocketAgent()
{
    this->m_clientSocket = nullptr;
    this->m_clientDataHandleThread = nullptr;
    this->m_pauseTimer = nullptr;
    this->m_notouchTimer = nullptr;
    this->m_cmdPipeDataHandleThread = nullptr;
    this->m_audioPipeDataHandleThread = nullptr;
    this->m_videoPipeDataHandleThread = nullptr;
    this->m_virtualDeviceDataHandleThread = nullptr;
    this->m_trailTimer = nullptr;
    pthread_mutex_init(&m_lock, nullptr);
    pthread_mutex_init(&m_clientSocketLock, nullptr);
    this->m_notice = nullptr;
    DisableSend();
}

CaeSocketAgent::~CaeSocketAgent()
{
    pthread_mutex_destroy(&m_lock);
    pthread_mutex_destroy(&m_clientSocketLock);
    if (m_clientSocket != nullptr) {
        delete m_clientSocket;
        m_clientSocket = nullptr;
    }
    if (m_notice != nullptr) {
        free(m_notice);
        m_notice = nullptr;
    }
    if (m_pauseTimer != nullptr) {
        delete m_pauseTimer;
        m_pauseTimer = nullptr;
    }
    if (m_trailTimer != nullptr) {
        delete m_trailTimer;
        m_trailTimer = nullptr;
    }
    if (m_notouchTimer != nullptr) {
        delete m_notouchTimer;
        m_notouchTimer = nullptr;
    }
    DestroyThreads();
}

int CaeSocketAgent::RecvCmdFromRTSA(void *pkt, size_t size)
{
    size_t msgHeaderSize = sizeof(STREAM_MSG_HEAD);
    if (pkt == nullptr || size <= msgHeaderSize) {
        LOGE("RTSA recv msg, size error!");
        return -1;
    }
    StreamMsgHead *msgHeader = static_cast<StreamMsgHead*>(pkt);
    if (msgHeader->type == CAE_MSG_AUDIO || msgHeader->type == CAE_MSG_VIDEO) {
        LOGE("RTSA recv msg, type error:%d", msgHeader->type);
        return -2;
    }

    if (m_clientDataHandleThread == nullptr) {
        LOGE("RTSA recv msg, m_clientDataHandleThread is nullptr");
        return -3;
    }
    m_clientDataHandleThread->HandleRTSACommandPkt(pkt, size);
    return size;
}

int CaeSocketAgent::Send(void *pkt, size_t size)
{
    int ret = -1;
    if (CaeSocketAgent::GetInstance().IsSocketValid() && m_clientSocket->GetStatus() != SOCKET_STATUS_ERROR) {
        ret = m_clientSocket->Send(pkt, size);
        if (ret < 0) {
            LOGE("Failed to send pkt.");
        }
    }
    return ret;
}

int CaeSocketAgent::Recv(void *pPkt, size_t size)
{
    int ret = -1;
    if (CaeSocketAgent::GetInstance().IsSocketValid() && m_clientSocket->GetStatus() != SOCKET_STATUS_ERROR) {
        ret = m_clientSocket->Recv(pPkt, size);
        if (ret < 0) {
            LOGD("No data to receive.");
        }
    }
    return ret;
}

void CaeSocketAgent::CreateClientNotice()
{
    if (CaeAgent::GetInstance().GetStatus() == CaeAgentStatus::CAE_AGENT_INIT) {
        if (m_notice != nullptr) {
            auto *notice = (CaeClientNotice *)m_notice;
            delete notice;
            m_notice = nullptr;
        }
        m_notice = (void *)new CaeClientNotice();
        if (m_notice == nullptr) {
            LOGE("Create m_notice failed.");
        }
    }
}

void CaeSocketAgent::SetClientSocket(CaeSocket *socket)
{
    LOGI("Start setting client sockets.");
    pthread_mutex_lock(&m_lock);
    if (socket == nullptr) {
        pthread_mutex_unlock(&m_lock);
        LOGI("End Setting Client Sockets.");
        return;
    }

    LOGI("CaeAgent's status is %d", CaeAgent::GetInstance().GetStatus());
#ifdef RTSA_SUPPORT
    if (!CaeConfigManage::GetRTSAEnable()) {
        CreateClientNotice();
    }
#else
    CreateClientNotice();
#endif

    m_clientSocket = socket;
    m_clientSocket->SetSocketOption(SOCKET_OPTION_BITSET_NODELAY | SOCKET_OPTION_BITSET_QUICKACK);
    m_clientSocket->SetSocketTimeout(1, 0);
    if (CaeAgent::GetInstance().GetStatus() == CaeAgentStatus::CAE_AGENT_INIT) {
        m_clientSocket->SetEventNotice((CaeEventNotice *) m_notice);
    } else if (CaeAgent::GetInstance().GetStatus() == CaeAgentStatus::CAE_AGENT_WAIT_RECONNECT) {
        m_clientSocket->SetEventNotice((CaeEventNotice *) m_notice);
        CaeSocketAgent::GetInstance().RestartErrorTimer();
    } else {
        LOGD("Nothing todo.");
    }
    LOGD("End to set new socket:%x", socket->GetFd());
    EnableSend();

    if (CaeAgent::GetInstance().GetStatus() == CaeAgentStatus::CAE_AGENT_INIT ||
        CaeAgent::GetInstance().GetStatus() == CaeAgentStatus::CAE_AGENT_WAIT_RECONNECT) {

        if (m_clientDataHandleThread == nullptr) {
            m_clientDataHandleThread = new(std::nothrow) CaeClientDataHandleThread();
            if (m_clientDataHandleThread == nullptr) {
                LOGE("Failed to create m_clientDataHandleThread.");
            } else {
                m_clientDataHandleThread->Start();
                LOGI("Start m_clientDataHandleThread. ");
            }
        }

        if (m_cmdPipeDataHandleThread == nullptr) {
            m_cmdPipeDataHandleThread =
                    new(std::nothrow) CaeControlCmdHandleThread(
                            CaeAgent::GetInstance().GetDataPipe(CAE_MSG_CONTROL));
            if (m_cmdPipeDataHandleThread == nullptr) {
                LOGE("Failed to create m_cmdPipeDataHandleThread.");
            } else {
                m_cmdPipeDataHandleThread->Start();
                LOGI("Start m_cmdPipeDataHandleThread. ");
            }
        }

        if (m_videoPipeDataHandleThread == nullptr) {
            m_videoPipeDataHandleThread =
                    new(std::nothrow) CaeVideoPipeDataHandleThread(
                            CaeAgent::GetInstance().GetDataPipe(CAE_MSG_VIDEO));
            if (m_videoPipeDataHandleThread == nullptr) {
                LOGE("Failed to create m_videoPipeDataHandleThread.");
            } else {
                m_videoPipeDataHandleThread->Start();
                LOGI("Start m_videoPipeDataHandleThread. ");
            }
        }

        if (m_audioPipeDataHandleThread == nullptr) {
            m_audioPipeDataHandleThread =
                    new(std::nothrow) CaeAudioPipeDataHandleThread(
                            CaeAgent::GetInstance().GetDataPipe(CAE_MSG_AUDIO));
            if (m_audioPipeDataHandleThread == nullptr) {
                LOGE("Failed to create m_audioPipeDataHandleThread .");
            } else {
                m_audioPipeDataHandleThread->Start();
                LOGI("Start m_audioPipeDataHandleThread.");
            }
        }

        if (CaeConfigManage::EnableVirtualDevice()) {
            if (m_virtualDeviceDataHandleThread == nullptr) {
                m_virtualDeviceDataHandleThread =
                        new(std::nothrow) CaeVirtualDeviceDataHandlerThread(
                                CaeAgent::GetInstance().GetDataPipe(CAE_MSG_VIRTUAL_DEVICE));
                if (m_virtualDeviceDataHandleThread == nullptr) {
                    LOGE("Failed to create m_virtualDeviceDataHandleThread .");
                } else {
                    m_virtualDeviceDataHandleThread->Start();
                    LOGI("Start m_virtualDeviceDataHandleThread.");
                }
            }
        }
    }

    pthread_mutex_unlock(&m_lock);
    LOGI("End Setting Client Sockets.");
}

void CaeSocketAgent::Reset()
{
    LOGI("Start resetting.");
    pthread_mutex_lock(&m_lock);
    Result closeRet = CaeEngineControl::GetInstance().CloseMediaStream();
    if (closeRet != SUCCESS) {
        LOGE("Failed to close the media stream.");
    }
    CaeEngineControl::GetInstance().CloseVirtualDevices();

    if (CaeConfigManage::EnableRemoteIme()) {
        CaeImeControl::GetInstance().DisableRemoteIme();
    }

    this->DestroyThreads();
    if (m_clientSocket != nullptr) {
        m_clientSocket->SetEventNotice(nullptr);
        m_clientSocket->CloseSocket();
        // RTSA退出逻辑不退出房间
#ifdef RTSA_SUPPORT        
        if (!CaeConfigManage::GetRTSAEnable()) {
            delete m_clientSocket;
            m_clientSocket = nullptr;
        }
#else
        delete m_clientSocket;
        m_clientSocket = nullptr;
#endif
    }
    
    if (nullptr != m_notice) {
        auto *notice = (CaeClientNotice *) m_notice;
        delete notice;
        m_notice = nullptr;
    }
    StopPauseTimer();

    StopTrailTimer();

    StopNotouchTimer();

    CaeTimerManage::GetInstance().DeleteAll();
    pthread_mutex_unlock(&m_lock);
    LOGI("End resetting.");
}

void CaeSocketAgent::DisableSend()
{
    LockClientSocket();
    this->m_sendFlag = false;
    UnlockClientSocket();
    LOGI("CAE can't use client socket.");
}

void CaeSocketAgent::EnableSend()
{
    LockClientSocket();
    this->m_sendFlag = true;
    UnlockClientSocket();
    LOGI("CAE can use client socket.");
}

void CaeSocketAgent::RestartErrorTimer()
{
    if (m_notice != nullptr) {
        auto *notice = (CaeClientNotice *)m_notice;
        notice->RestartErrorTimer();
    }
}

void CaeSocketAgent::CloseOldClient()
{
    LOGI("Start closing old client.");
    pthread_mutex_lock(&m_lock);
    Result closeRet = CaeEngineControl::GetInstance().CloseMediaStream();
    if (closeRet != SUCCESS) {
        LOGE("Failed to close media stream.");
    }
    CaeEngineControl::GetInstance().CloseVirtualDevices();
    DestroyThreads();
    CloseOldSocket();
    CaeAgent::GetInstance().ClearPipesData();
    // RTSA退出逻辑不退出房间
    if (m_clientSocket != nullptr) {
#ifdef RTSA_SUPPORT
        if (!CaeConfigManage::GetRTSAEnable()) {
            delete m_clientSocket;
            m_clientSocket = nullptr;
        }
#else
        delete m_clientSocket;
        m_clientSocket = nullptr;
#endif
    }

    if (CaeAgent::GetInstance().GetStatus() == CaeAgentStatus::CAE_AGENT_PAUSE) {
        StartPauseTimer();
    }
    pthread_mutex_unlock(&m_lock);
    LOGI("End closing old client.");
}

void CaeSocketAgent::PauseClient()
{
    LOGI("Start pausing client.");
    pthread_mutex_lock(&m_lock);
    Result closeRet = CaeEngineControl::GetInstance().CloseMediaStream();
    if (closeRet != SUCCESS) {
        LOGE("Failed to closing media stream.");
    }
    CaeEngineControl::GetInstance().CloseVirtualDevices();
    StartPauseTimer();
    pthread_mutex_unlock(&m_lock);
    LOGI("End pausing client.");
}

void CaeSocketAgent::CloseOldSocket()
{
    if (m_clientSocket != nullptr) {
        m_clientSocket->SetEventNotice(nullptr);
        m_clientSocket->CloseSocket();
    }
}

void CaeSocketAgent::UpdateErrorTimerTime()
{
    if (CaeAgent::GetInstance().GetStatus() == CaeAgentStatus::CAE_AGENT_WAIT_RECONNECT) {
        return;
    }
    auto *notice = (CaeClientNotice *)m_notice;
    if (notice == nullptr || notice->m_errorTimer == nullptr) {
        return;
    }
    gettimeofday(&(notice->m_errorTimer->m_lasttime), nullptr);
    if (notice->m_errorTimer->m_disconnectTimes > 1 &&
        notice->m_errorTimer->m_disconnectTimes < notice->m_errorTimer->GetDisconnectTimesMax()) {
        notice->m_errorTimer->Restart();
    }
}

void CaeSocketAgent::UpdateNoTouchTimer()
{
    if (m_notouchTimer != nullptr) {
        this->m_notouchTimer->Restart();
    }
}

bool CaeSocketAgent::StartNotouchTimer()
{
    StopNotouchTimer();
    auto touchTimeout = static_cast<uint32_t>(atoi(CaeParamStorage::GetInstance().GetTouchTimeout().c_str()));
    if (touchTimeout > 0) {
        LOGD("Before starting, CaeTimerManage's size is %d.",
             CaeTimerManage::GetInstance().m_tVec.size());
        uint32_t interval = CaeConfigManage::GetOutTimes() *
                                        CaeConfigManage::GetBaseTime();
        if (interval <= 0 || interval > MAX_INTERVAL) {
            LOGE("Failed to start no touch timer, interval is %d. interval is invalid!", interval);
            interval = 1000;
        }
        uint32_t timeout =
                static_cast<uint32_t>(atoi(CaeParamStorage::GetInstance().GetTouchTimeout().c_str()));
        m_notouchTimer = new (std::nothrow) CaeNotouchTimer(interval, timeout);
        if (m_notouchTimer == nullptr) {
            LOGE("Failed to start no touch timer, failed to create no touch timer .");
            return false;
        }
        m_notouchTimer->Start();
        CaeTimerManage::GetInstance().AddTimer(m_notouchTimer);
        LOGD("After startup, CaeTimerManage's size is %d", CaeTimerManage::GetInstance().m_tVec.size());
    }
    return true;
}

void CaeSocketAgent::StopNotouchTimer()
{
    if (m_notouchTimer != nullptr) {
        LOGI("Start stop NotouchTimer.");
        CaeTimerManage::GetInstance().DeleteTimer((CaeTimer *)m_notouchTimer);
        m_notouchTimer = nullptr;
        LOGI("End stop NotouchTimer.");
    }
    CaeClientNotifyTool::GetInstance().ResetTouchTimeout();
}

void CaeSocketAgent::StartPauseTimer()
{
    LOGD("Before starting, CaeTimerManage's size is %d.", CaeTimerManage::GetInstance().m_tVec.size());
    if (m_pauseTimer != nullptr) {
        return;
    }
    uint32_t timeoutSec =
        static_cast<uint32_t>(atoi(CaeParamStorage::GetInstance().GetBackgroundTimeout().c_str()));
    m_pauseTimer = new CaeTcpClientPauseTimer(CaeConfigManage::GetOutTimes() *
                                              CaeConfigManage::GetBaseTime(), timeoutSec);
    m_pauseTimer->Start();
    CaeTimerManage::GetInstance().AddTimer((CaeTimer *) m_pauseTimer);
    LOGD("after StartPauseTimer, CaeTimerManage'size is %d.", CaeTimerManage::GetInstance().m_tVec.size());
}

void CaeSocketAgent::StopPauseTimer()
{
    if (m_pauseTimer != nullptr) {
        LOGI("Start stop PauseTimer.");
        CaeTimerManage::GetInstance().DeleteTimer((CaeTimer *)m_pauseTimer);
        m_pauseTimer = nullptr;
        LOGI("End stop PauseTimer.");
    }
}

bool CaeSocketAgent::StartTrailTimer()
{
    if (m_trailTimer != nullptr) {
        LOGI("Start stopping the old trail timer.");
        CaeTimerManage::GetInstance().DeleteTimer((CaeTimer *)m_trailTimer);
        m_trailTimer = nullptr;
        LOGI("End to stop the old trail timer.");
    }

    uint32_t trialTime =
        static_cast<uint32_t>(atoi(CaeParamStorage::GetInstance().GetAvailablePlayTime().c_str()));
    LOGI("Start trail timer = %d", trialTime);
    if (trialTime > 0) {
        m_trailTimer = new (std::nothrow) CaeTrialTimeoutTimer(trialTime);
        if (m_trailTimer == nullptr) {
            LOGE("Failed to start trail timer, failed to create trail timer.");
            return false;
        }
        m_trailTimer->Start();
        CaeTimerManage::GetInstance().AddTimer((CaeTimer *) m_trailTimer);
    }
    return true;
}

void CaeSocketAgent::StopTrailTimer()
{
    LOGI("Stop trail timer.");
    if (m_trailTimer != nullptr) {
        CaeTimerManage::GetInstance().DeleteTimer((CaeTimer *)m_trailTimer);
        m_trailTimer = nullptr;
    }
    CaeClientNotifyTool::GetInstance().ResetTrailTimeout();
}

void CaeSocketAgent::DestroyThreads() noexcept
{
    if (m_videoPipeDataHandleThread != nullptr) {
        m_videoPipeDataHandleThread->Stop();
        while (m_videoPipeDataHandleThread->m_threadStatus != CAE_THREAD_STATUS_EXIT) {
            usleep(MAX_INTERVAL);
        }
        delete m_videoPipeDataHandleThread;
        m_videoPipeDataHandleThread = nullptr;
    }
    if (m_audioPipeDataHandleThread != nullptr) {
        m_audioPipeDataHandleThread->Stop();
        while (m_audioPipeDataHandleThread->m_threadStatus != CAE_THREAD_STATUS_EXIT) {
            usleep(MAX_INTERVAL);
        }
        delete m_audioPipeDataHandleThread;
        m_audioPipeDataHandleThread = nullptr;
    }
    if (m_clientDataHandleThread != nullptr) {
        m_clientDataHandleThread->Stop();
        while (m_clientDataHandleThread->m_threadStatus != CAE_THREAD_STATUS_EXIT) {
            usleep(10);
        }
        delete m_clientDataHandleThread;
        m_clientDataHandleThread = nullptr;
    }
    if (m_cmdPipeDataHandleThread != nullptr) {
        m_cmdPipeDataHandleThread->Stop();
        while (m_cmdPipeDataHandleThread->m_threadStatus != CAE_THREAD_STATUS_EXIT) {
            usleep(10);
        }
        delete m_cmdPipeDataHandleThread;
        m_cmdPipeDataHandleThread = nullptr;
    }
    if (m_virtualDeviceDataHandleThread != nullptr) {
        m_virtualDeviceDataHandleThread->Stop();
        while (m_virtualDeviceDataHandleThread->m_threadStatus != CAE_THREAD_STATUS_EXIT) {
            usleep(10);
        }
        delete m_virtualDeviceDataHandleThread;
        m_virtualDeviceDataHandleThread = nullptr;
    }
}

void CaeSocketAgent::SendVirtualDeviceStartRequest()
{
    if (m_virtualDeviceDataHandleThread != nullptr) {
        if (CaeVirtualDeviceConfig::IsOpenCamera()) {
            LOGI("Camera is open, need send start request.");
            m_virtualDeviceDataHandleThread->HandleStartPreviewReq(CaeVirtualDeviceConfig::GetCameraTmpHeader(),
                                  CaeVirtualDeviceConfig::GetCameraTmpData(),
                                  CaeVirtualDeviceConfig::GetCameraTmpPipeData());
        }

        if (CaeVirtualDeviceConfig::IsOpenMicrophone()) {
            LOGI("Microphone is open, need send start request.");
            m_virtualDeviceDataHandleThread->HandleStartRecordReq(CaeVirtualDeviceConfig::GetMicrophoneTmpHeader(),
                                 CaeVirtualDeviceConfig::GetMicrophoneTmpData(),
                                 CaeVirtualDeviceConfig::GetMicrophoneTmpPipeData());
        }
    }
}